/*
 * RT-Thread Secure
 *
 * Copyright (c) 2021, Shanghai Real-Thread Electronic Technology Co., Ltd.
 *
 * All rights reserved.
 */
#include "rk3568_iomux.h"
#include "board.h"
#include "rk3568.h"
#include "rtdef.h"

static rt_uint64_t mux_base[2] = {0};

int iomux_get_pull_mode(gpio_port_t port, gpio_pin_t pin, gpio_index_t index)
{
    rt_uint32_t reg = 0;
    rt_uint32_t pull_offset = 0;
    rt_uint32_t mask = 0, shift = 0;
    gpio_pull_mode_t pull_mode = 0;
    rt_uint32_t seg_addr = 0;
    rt_uint64_t base_addr = 0;

    if(port > GPIO_PORT_NUM)
    {
        rt_kprintf("GPIO port error\n");
        return -RT_EINVAL;
    }
    else if(pin > GPIO_PIN_NUM)
    {
        rt_kprintf("GPIO pin error\n");
        return -RT_EINVAL;
    }
    else if(index > GPIO_INDEX_NUM)
    {
        rt_kprintf("GPIO index error\n");
        return -RT_EINVAL;
    }

    if(port == 0)
    {
        seg_addr = 0x20;
        base_addr = mux_base[0];
    }
    else
    {
        seg_addr = 0x80;
        base_addr = mux_base[1];
        port -= 1;
    }

    pull_offset = seg_addr + (port*0x0010) + (pin*0x0004);
    reg = readl(base_addr + pull_offset);

    shift = 2 * index;
    mask = 0x3 << shift;
    pull_mode = (reg & mask) >> shift;

    return pull_mode;
}

int iomux_set_pull_mode(gpio_port_t port, gpio_pin_t pin, gpio_index_t index, gpio_pull_mode_t pull_mode)
{
    rt_uint32_t reg = 0;
    rt_uint32_t pull_offset = 0;
    rt_uint32_t mask = 0, shift = 0;
    rt_uint32_t seg_addr = 0;
    rt_uint64_t base_addr = 0;

    if(port > GPIO_PORT_NUM)
    {
        rt_kprintf("GPIO port error\n");
        return -RT_EINVAL;
    }
    else if(pin > GPIO_PIN_NUM)
    {
        rt_kprintf("GPIO pin error\n");
        return -RT_EINVAL;
    }
    else if(index > GPIO_INDEX_NUM)
    {
        rt_kprintf("GPIO index error\n");
        return -RT_EINVAL;
    }

    if(port == 0)
    {
        seg_addr = 0x20;
        base_addr = mux_base[0];
    }
    else
    {
        seg_addr = 0x80;
        base_addr = mux_base[1];
        port -= 1;
    }

    pull_offset = seg_addr + (port*0x0010) + (pin*0x0004);
    reg = readl(base_addr + pull_offset);

    shift = 2 * index;
    mask = 0x3 << shift;

    reg &= ~(mask);
    reg |= (0x30000 | pull_mode) << shift;

    writel(reg, base_addr + pull_offset);
    writel(reg & 0x0000FFFF, base_addr + pull_offset);

    return RT_EOK;
}

int iomux_get_drive_level(gpio_port_t port, gpio_pin_t pin, gpio_index_t index)
{
    rt_uint32_t reg = 0;
    rt_uint32_t level_offset = 0;
    rt_uint32_t mask = 0, shift = 0;
    gpio_drv_level_t drive_level = 0;
    rt_uint32_t seg_addr = 0;
    rt_uint64_t base_addr = 0;

    if(port > GPIO_PORT_NUM)
    {
        rt_kprintf("GPIO port error\n");
        return -RT_EINVAL;
    }
    else if(pin > GPIO_PIN_NUM)
    {
        rt_kprintf("GPIO pin error\n");
        return -RT_EINVAL;
    }
    else if(index > GPIO_INDEX_NUM)
    {
        rt_kprintf("GPIO index error\n");
        return -RT_EINVAL;
    }

    if(port == 0)
    {
        seg_addr = 0x70;
        base_addr = mux_base[0];
    }
    else
    {
        seg_addr = 0x200;
        base_addr = mux_base[1];
    }

    level_offset
     = seg_addr + (pin*0x0010) + ((index >> 1) * 0x0004);
    reg = readl(base_addr + level_offset);

    shift = 8 * (index % 2);
    mask = 0x3f << shift;

    drive_level = (reg & mask) >> shift;

    return drive_level;
}

int iomux_set_drive_level(gpio_port_t port, gpio_pin_t pin, gpio_index_t index, gpio_drv_level_t level)
{
    rt_uint32_t reg = 0;
    rt_uint32_t level_offset = 0;
    rt_uint32_t mask = 0, shift = 0;
    rt_uint32_t seg_addr = 0;
    rt_uint64_t base_addr = 0;

    if(port > GPIO_PORT_NUM)
    {
        rt_kprintf("GPIO port error\n");
        return -RT_EINVAL;
    }
    else if(pin > GPIO_PIN_NUM)
    {
        rt_kprintf("GPIO pin error\n");
        return -RT_EINVAL;
    }
    else if(index > GPIO_INDEX_NUM)
    {
        rt_kprintf("GPIO index error\n");
        return -RT_EINVAL;
    }

    if(port == 0)
    {
        seg_addr = 0x70;
        base_addr = mux_base[0];
    }
    else
    {
        seg_addr = 0x200;
        base_addr = mux_base[1];
    }

    level_offset = seg_addr + (pin*0x0010) + ((index >> 1) * 0x0004);
    reg = readl(base_addr + level_offset);

    shift = 8 * (index % 2);
    mask = 0x3F << shift;

    reg &= ~(mask);
    reg |= (0x3F0000 | level) << shift;

    writel(reg, base_addr + level_offset);
    writel(reg & 0x0000FFFF, base_addr + level_offset);

    return RT_EOK;
}

int iomux_get_speed(gpio_port_t port, gpio_pin_t pin, gpio_index_t index)
{
    rt_uint32_t reg = 0;
    rt_uint32_t speed_offset = 0;
    rt_uint32_t mask = 0, shift = 0;
    gpio_speed_t gpio_speed = 0;
    rt_uint32_t seg_addr = 0;
    rt_uint64_t base_addr = 0;

    if(port > GPIO_PORT_NUM)
    {
        rt_kprintf("GPIO port error\n");
        return -RT_EINVAL;
    }
    else if(pin > GPIO_PIN_NUM)
    {
        rt_kprintf("GPIO pin error\n");
        return -RT_EINVAL;
    }
    else if(index > GPIO_INDEX_NUM)
    {
        rt_kprintf("GPIO index error\n");
        return -RT_EINVAL;
    }

    if(port == 0)
    {
        seg_addr = 0x60;
        base_addr = mux_base[0];
    }
    else
    {
        seg_addr = 0x180;
        base_addr = mux_base[1];
        port -= 1;
    }

    speed_offset = seg_addr + (port*0x0010) + (pin*0x0004);
    reg = readl(base_addr + speed_offset);

    shift = 2 * index;
    mask = 0x3 << shift;
    gpio_speed = (reg & mask) >> shift;

    return gpio_speed;
}

int iomux_set_speed(gpio_port_t port, gpio_pin_t pin, gpio_index_t index, gpio_speed_t speed)
{
    rt_uint32_t reg = 0;
    rt_uint32_t speed_offset = 0;
    rt_uint32_t mask = 0, shift = 0;
    rt_uint32_t seg_addr = 0;
    rt_uint64_t base_addr = 0;

    if(port > GPIO_PORT_NUM)
    {
        rt_kprintf("GPIO port error\n");
        return -RT_EINVAL;
    }
    else if(pin > GPIO_PIN_NUM)
    {
        rt_kprintf("GPIO pin error\n");
        return -RT_EINVAL;
    }
    else if(index > GPIO_INDEX_NUM)
    {
        rt_kprintf("GPIO index error\n");
        return -RT_EINVAL;
    }

    if(port == 0)
    {
        seg_addr = 0x60;
        base_addr = mux_base[0];
    }
    else
    {
        seg_addr = 0x180;
        base_addr = mux_base[1];
        port -= 1;
    }

    speed_offset = seg_addr + (port*0x0010) + (pin*0x0004);
    reg = readl(base_addr + speed_offset);

    shift = 2 * index;
    mask = 0x3 << shift;

    reg &= ~(mask);
    reg |= (0x30000 | speed) << shift;

    writel(reg, base_addr + speed_offset);
    writel(reg & 0x0000FFFF, base_addr + speed_offset);

    return RT_EOK;
}

int iomux_get_schmitt_trigger(gpio_port_t port, gpio_pin_t pin, gpio_index_t index)
{
    rt_uint32_t reg = 0;
    rt_uint32_t schmitt_offset = 0;
    rt_uint32_t mask = 0, shift = 0;
    gpio_schmitt_trigger_t schmitt_trigger = 0;
    rt_uint32_t seg_addr = 0;
    rt_uint64_t base_addr = 0;

    if(port > GPIO_PORT_NUM)
    {
        rt_kprintf("GPIO port error\n");
        return -RT_EINVAL;
    }
    else if(pin > GPIO_PIN_NUM)
    {
        rt_kprintf("GPIO pin error\n");
        return -RT_EINVAL;
    }
    else if(index > GPIO_INDEX_NUM)
    {
        rt_kprintf("GPIO index error\n");
        return -RT_EINVAL;
    }

    if(port == 0)
    {
        seg_addr = 0x30;
        base_addr = mux_base[0];
    }
    else
    {
        seg_addr = 0xC0;
        base_addr = mux_base[1];
        port -= 1;
    }

    schmitt_offset = seg_addr + (port*0x0010) + (pin*0x0004);
    reg = readl(base_addr + schmitt_offset);

    shift = 2 * index;
    mask = 0x3 << shift;

    schmitt_trigger = (reg & mask) >> shift;

    return schmitt_trigger;
}

int iomux_set_schmitt_trigger(gpio_port_t port, gpio_pin_t pin, gpio_index_t index, gpio_schmitt_trigger_t smt)
{
    rt_uint32_t reg = 0;
    rt_uint32_t schmitt_offset = 0;
    rt_uint32_t mask = 0, shift = 0;
    rt_uint32_t seg_addr = 0;
    rt_uint64_t base_addr = 0;

    if(port > GPIO_PORT_NUM)
    {
        rt_kprintf("GPIO port error\n");
        return -RT_EINVAL;
    }
    else if(pin > GPIO_PIN_NUM)
    {
        rt_kprintf("GPIO pin error\n");
        return -RT_EINVAL;
    }
    else if(index > GPIO_INDEX_NUM)
    {
        rt_kprintf("GPIO index error\n");
        return -RT_EINVAL;
    }

    if(port == 0)
    {
        seg_addr = 0x30;
        base_addr = mux_base[0];
    }
    else
    {
        seg_addr = 0xC0;
        base_addr = mux_base[1];
        port -= 1;
    }

    schmitt_offset = seg_addr + (port*0x0010) + (pin*0x0004);
    reg = readl(base_addr + schmitt_offset);

    shift = 2 * index;
    mask = 0x3 << shift;

    reg &= ~(mask);
    reg |= (0x30000 | smt) << shift;

    writel(reg, base_addr + schmitt_offset);
    writel(reg & 0x0000FFFF, base_addr + schmitt_offset);

    return RT_EOK;
}

int iomux_get_open_drain_mode(gpio_port_t port, gpio_pin_t pin, gpio_index_t index)
{
    rt_uint32_t reg = 0;
    rt_uint32_t od_offset = 0;
    rt_uint32_t mask = 0, shift = 0;
    gpio_od_mode_t open_drain = 0;
    rt_uint32_t seg_addr = 0;
    rt_uint64_t base_addr = 0;

    if(port > GPIO_PORT_NUM)
    {
        rt_kprintf("GPIO port error\n");
        return -RT_EINVAL;
    }
    else if(pin > GPIO_PIN_NUM)
    {
        rt_kprintf("GPIO pin error\n");
        return -RT_EINVAL;
    }
    else if(index > GPIO_INDEX_NUM)
    {
        rt_kprintf("GPIO index error\n");
        return -RT_EINVAL;
    }

    if(port == 0)
    {
        seg_addr = 0x40;
        base_addr = mux_base[0];
    }
    else
    {
        seg_addr = 0x100;
        base_addr = mux_base[1];
        port -= 1;
    }

    od_offset = seg_addr + (port*0x0010) + (pin*0x0004);
    reg = readl(base_addr + od_offset);

    shift = index;
    mask = 0x1 << shift;

    open_drain = (reg & mask) >> shift;

    return open_drain;
}

int iomux_set_open_drain_mode(gpio_port_t port, gpio_pin_t pin, gpio_index_t index, gpio_od_mode_t od)
{
    rt_uint32_t reg = 0;
    rt_uint32_t od_offset = 0;
    rt_uint32_t mask = 0, shift = 0;
    rt_uint32_t seg_addr = 0;
    rt_uint64_t base_addr = 0;

    if(port > GPIO_PORT_NUM)
    {
        rt_kprintf("GPIO port error\n");
        return -RT_EINVAL;
    }
    else if(pin > GPIO_PIN_NUM)
    {
        rt_kprintf("GPIO pin error\n");
        return -RT_EINVAL;
    }
    else if(index > GPIO_INDEX_NUM)
    {
        rt_kprintf("GPIO index error\n");
        return -RT_EINVAL;
    }

    if(port == 0)
    {
        seg_addr = 0x40;
        base_addr = mux_base[0];
    }
    else
    {
        seg_addr = 0x100;
        base_addr = mux_base[1];
        port -= 1;
    }

    od_offset = seg_addr + (port*0x0010) + (pin*0x0004);
    reg = readl(base_addr + od_offset);

    shift = index;
    mask = 0x1 << shift;

    reg &= ~(mask);
    reg |= (0x10000 | od) << shift;

    writel(reg, base_addr + od_offset);
    writel(reg & 0x0000FFFF, base_addr + od_offset);

    return RT_EOK;
}

int iomux_get_iomux_sel(gpio_port_t port, gpio_pin_t pin, gpio_index_t index)
{
    rt_uint32_t reg = 0;
    rt_uint32_t iomux_offset = 0;
    rt_uint32_t mask = 0, shift = 0;
    gpio_iomux_sel_t iomux_sel = 0;
    rt_uint32_t seg_addr = 0;
    rt_uint64_t base_addr = 0;

    if(port > GPIO_PORT_NUM)
    {
        rt_kprintf("GPIO port error\n");
        return -RT_EINVAL;
    }
    else if(pin > GPIO_PIN_NUM)
    {
        rt_kprintf("GPIO pin error\n");
        return -RT_EINVAL;
    }
    else if(index > GPIO_INDEX_NUM)
    {
        rt_kprintf("GPIO index error\n");
        return -RT_EINVAL;
    }

    if(port == 0)
    {
        seg_addr = 0x00;
        base_addr = mux_base[0];
    }
    else
    {
        seg_addr = 0x00;
        base_addr = mux_base[1];
        port -= 1;
    }

    iomux_offset = seg_addr + (port*0x0020) + (pin*0x0008) + ((index >> 2) * 0x0004);
    reg = readl(base_addr + iomux_offset);

    if(index < 4)
    {
        shift = 4 * index;
    }
    else
    {
        shift = 4 * (index - 4);
    }

    mask = 0x7 << shift;

    iomux_sel = (reg & mask) >> shift;

    return iomux_sel;
}

int iomux_set_iomux_sel(gpio_port_t port, gpio_pin_t pin, gpio_index_t index, gpio_iomux_sel_t iomux_sel)
{
    rt_uint32_t reg = 0;
    rt_uint32_t iomux_offset = 0;
    rt_uint32_t mask = 0, shift = 0;
    rt_uint32_t seg_addr = 0;
    rt_uint64_t base_addr = 0;

    if(port > GPIO_PORT_NUM)
    {
        rt_kprintf("GPIO port error\n");
        return -RT_EINVAL;
    }
    else if(pin > GPIO_PIN_NUM)
    {
        rt_kprintf("GPIO pin error\n");
        return -RT_EINVAL;
    }
    else if(index > GPIO_INDEX_NUM)
    {
        rt_kprintf("GPIO index error\n");
        return -RT_EINVAL;
    }

    if(port == 0)
    {
        seg_addr = 0x00;
        base_addr = mux_base[0];
    }
    else
    {
        seg_addr = 0x00;
        base_addr = mux_base[1];
        port -= 1;
    }

    iomux_offset = seg_addr + (port*0x0020) + (pin*0x0008) + ((index >> 2) * 0x0004);
    reg = readl(base_addr + iomux_offset);

    if(index < 4)
    {
        shift = 4 * index;
    }
    else
    {
        shift = 4 * (index - 4);
    }

    mask = 0x7 << shift;

    reg &= ~(mask);
    reg |= (0x70000 | iomux_sel) << shift;

    writel(reg, base_addr + iomux_offset);
    writel(reg & 0x0000FFFF, base_addr + iomux_offset);

    return RT_EOK;
}

int iomux_set_mode(gpio_port_t port, gpio_pin_t pin, gpio_index_t index, gpio_iomux_sel_t iomux_sel,
    gpio_pull_mode_t pull_mode, gpio_drv_level_t level, gpio_speed_t speed, gpio_schmitt_trigger_t smt,
    gpio_od_mode_t od)
{
    int return_code = 0;

    return_code += iomux_set_pull_mode(port, pin, index, pull_mode);
    return_code += iomux_set_drive_level(port, pin, index, level);
    return_code += iomux_set_speed(port, pin, index, speed);
    return_code += iomux_set_schmitt_trigger(port, pin, index, smt);
    return_code += iomux_set_open_drain_mode(port, pin, index, od);
    return_code += iomux_set_iomux_sel(port, pin, index, iomux_sel);

    if(return_code != RT_EOK)
    {
        return_code = -RT_EIO;
    }

    return return_code;
}

int rk_iomux_init(void)
{
    mux_base[0] = rt_ioremap(GRF_PMU_BASE, 0x00010000);
    mux_base[1] = rt_ioremap(GRF_SYS_BASE, 0x00010000);

    // mux_base[0] = GRF_PMU_BASE;
    // mux_base[1] = GRF_SYS_BASE;

    return RT_EOK;
}
// INIT_DEVICE_EXPORT(rk_iomux_init);
